/* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Url Classifier code * * The Initial Developer of the Original Code is * Google Inc. * Portions created by the Initial Developer are Copyright (C) 2006 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Tony Chang <tony@ponderer.org> * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ // We wastefully reload the same JS files across components. This puts all // the common JS files used by safebrowsing and url-classifier into a // single component. const Cc = Components.classes; const Ci = Components.interfaces; const G_GDEBUG = false; // TODO: get rid of application.js and filesystem.js (not used much) /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Google Safe Browsing. * * The Initial Developer of the Original Code is Google Inc. * Portions created by the Initial Developer are Copyright (C) 2006 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Aaron Boodman <aa@google.com> (original author) * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ // This file has pure js helper functions. Hence you'll find metion // of browser-specific features in here. /** * lang.js - The missing JavaScript language features * * WARNING: This class adds members to the prototypes of String, Array, and * Function for convenience. * * The tradeoff is that the for/in statement will not work properly for those * objects when this library is used. * * To work around this for Arrays, you may want to use the forEach() method, * which is more fun and easier to read. */ /** * Returns true if the specified value is |null| */ function isNull(val) { return val === null; } /** * Returns true if the specified value is an array */ function isArray(val) { return isObject(val) && val.constructor == Array; } /** * Returns true if the specified value is a string */ function isString(val) { return typeof val == "string"; } /** * Returns true if the specified value is a boolean */ function isBoolean(val) { return typeof val == "boolean"; } /** * Returns true if the specified value is a number */ function isNumber(val) { return typeof val == "number"; } /** * Returns true if the specified value is a function */ function isFunction(val) { return typeof val == "function"; } /** * Returns true if the specified value is an object */ function isObject(val) { return val && typeof val == "object"; } /** * Returns an array of all the properties defined on an object */ function getObjectProps(obj) { var ret = []; for (var p in obj) { ret.push(p); } return ret; } /** * Returns true if the specified value is an object which has no properties * defined. */ function isEmptyObject(val) { if (!isObject(val)) { return false; } for (var p in val) { return false; } return true; } var getHashCode; var removeHashCode; (function () { var hashCodeProperty = "lang_hashCode_"; /** * Adds a lang_hashCode_ field to an object. The hash code is unique for the * given object. * @param obj {Object} The object to get the hash code for * @returns {Number} The hash code for the object */ getHashCode = function(obj) { // In IE, DOM nodes do not extend Object so they do not have this method. // we need to check hasOwnProperty because the proto might have this set. if (obj.hasOwnProperty && obj.hasOwnProperty(hashCodeProperty)) { return obj[hashCodeProperty]; } if (!obj[hashCodeProperty]) { obj[hashCodeProperty] = ++getHashCode.hashCodeCounter_; } return obj[hashCodeProperty]; }; /** * Removes the lang_hashCode_ field from an object. * @param obj {Object} The object to remove the field from. */ removeHashCode = function(obj) { obj.removeAttribute(hashCodeProperty); }; getHashCode.hashCodeCounter_ = 0; })(); /** * Fast prefix-checker. */ String.prototype.startsWith = function(prefix) { if (this.length < prefix.length) { return false; } if (this.substring(0, prefix.length) == prefix) { return true; } return false; } /** * Removes whitespace from the beginning and end of the string */ String.prototype.trim = function() { return this.replace(/^\s+|\s+$/g, ""); } /** * Does simple python-style string substitution. * "foo%s hot%s".subs("bar", "dog") becomes "foobar hotdot". * For more fully-featured templating, see template.js. */ String.prototype.subs = function() { var ret = this; // this appears to be slow, but testing shows it compares more or less equiv. // to the regex.exec method. for (var i = 0; i < arguments.length; i++) { ret = ret.replace(/\%s/, String(arguments[i])); } return ret; } /** * Returns the last element on an array without removing it. */ Array.prototype.peek = function() { return this[this.length - 1]; } // TODO(anyone): add splice the first time someone needs it and then implement // push, pop, shift, unshift in terms of it where possible. // TODO(anyone): add the other neat-o functional methods like map(), etc. /** * Partially applies this function to a particular "this object" and zero or * more arguments. The result is a new function with some arguments of the first * function pre-filled and the value of |this| "pre-specified". * * Remaining arguments specified at call-time are appended to the pre- * specified ones. * * Also see: partial(). * * Note that bind and partial are optimized such that repeated calls to it do * not create more than one function object, so there is no additional cost for * something like: * * var g = bind(f, obj); * var h = partial(g, 1, 2, 3); * var k = partial(h, a, b, c); * * Usage: * var barMethBound = bind(myFunction, myObj, "arg1", "arg2"); * barMethBound("arg3", "arg4"); * * @param thisObj {object} Specifies the object which |this| should point to * when the function is run. If the value is null or undefined, it will default * to the global object. * * @returns {function} A partially-applied form of the function bind() was * invoked as a method of. */ function bind(fn, self, opt_args) { var boundargs = (typeof fn.boundArgs_ != "undefined") ? fn.boundArgs_ : []; boundargs = boundargs.concat(Array.prototype.slice.call(arguments, 2)); if (typeof fn.boundSelf_ != "undefined") { self = fn.boundSelf_; } if (typeof fn.boundFn_ != "undefined") { fn = fn.boundFn_; } var newfn = function() { // Combine the static args and the new args into one big array var args = boundargs.concat(Array.prototype.slice.call(arguments)); return fn.apply(self, args); } newfn.boundArgs_ = boundargs; newfn.boundSelf_ = self; newfn.boundFn_ = fn; return newfn; } /** * An alias to the bind() global function. * * Usage: * var g = f.bind(obj, arg1, arg2); * g(arg3, arg4); */ Function.prototype.bind = function(self, opt_args) { return bind.apply( null, [this, self].concat(Array.prototype.slice.call(arguments, 1))); } /** * Like bind(), except that a "this object" is not required. Useful when the * target function is already bound. * * Usage: * var g = partial(f, arg1, arg2); * g(arg3, arg4); */ function partial(fn, opt_args) { return bind.apply( null, [fn, null].concat(Array.prototype.slice.call(arguments, 1))); } /** * An alias to the partial() global function. * * Usage: * var g = f.partial(arg1, arg2); * g(arg3, arg4); */ Function.prototype.partial = function(opt_args) { return bind.apply( null, [this, null].concat(Array.prototype.slice.call(arguments))); } /** * Convenience. Binds all the methods of obj to itself. Calling this in the * constructor before referencing any methods makes things a little more like * Java or Python where methods are intrinsically bound to their instance. */ function bindMethods(obj) { for (var p in obj) { if (isFunction(obj[p])) { obj[p] = obj[p].bind(obj); } } } /** * Inherit the prototype methods from one constructor into another. * * Usage: * <pre> * function ParentClass(a, b) { } * ParentClass.prototype.foo = function(a) { } * * function ChildClass(a, b, c) { * ParentClass.call(this, a, b); * } * * ChildClass.inherits(ParentClass); * * var child = new ChildClass("a", "b", "see"); * child.foo(); // works * </pre> * * In addition, a superclass' implementation of a method can be invoked * as follows: * * <pre> * ChildClass.prototype.foo = function(a) { * ChildClass.superClass_.foo.call(this, a); * // other code * }; * </pre> */ Function.prototype.inherits = function(parentCtor) { var tempCtor = function(){}; tempCtor.prototype = parentCtor.prototype; this.superClass_ = parentCtor.prototype; this.prototype = new tempCtor(); } //@line 48 "/cygdrive/c/builds/tinderbox/Fx-Mozilla1.8-release/WINNT_5.2_Depend/mozilla/toolkit/components/url-classifier/src/nsUrlClassifierLib.js" /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Google Safe Browsing. * * The Initial Developer of the Original Code is Google Inc. * Portions created by the Initial Developer are Copyright (C) 2006 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Fritz Schneider <fritz@google.com> (original author) * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ // Class for manipulating preferences. Aside from wrapping the pref // service, useful functionality includes: // // - abstracting prefobserving so that you can observe preferences // without implementing nsIObserver // // - getters that return a default value when the pref doesn't exist // (instead of throwing) // // - get-and-set getters // // Example: // // var p = new PROT_Preferences(); // alert(p.getPref("some-true-pref")); // shows true // alert(p.getPref("no-such-pref", true)); // shows true // alert(p.getPref("no-such-pref", null)); // shows null // // function observe(prefThatChanged) { // alert("Pref changed: " + prefThatChanged); // }; // // p.addObserver("somepref", observe); // p.setPref("somepref", true); // alerts // p.removeObserver("somepref", observe); // // TODO: should probably have the prefobserver pass in the new and old // values // TODO(tc): Maybe remove this class and just call natively since we're no // longer an extension. /** * A class that wraps the preferences service. * * @param opt_startPoint A starting point on the prefs tree to resolve * names passed to setPref and getPref. * * @param opt_useDefaultPranch Set to true to work against the default * preferences tree instead of the profile one. * * @constructor */ function G_Preferences(opt_startPoint, opt_getDefaultBranch) { this.debugZone = "prefs"; this.observers_ = {}; this.getDefaultBranch_ = !!opt_getDefaultBranch; this.startPoint_ = opt_startPoint || null; } G_Preferences.setterMap_ = { "string": "setCharPref", "boolean": "setBoolPref", "number": "setIntPref" }; G_Preferences.getterMap_ = {}; G_Preferences.getterMap_[Ci.nsIPrefBranch.PREF_STRING] = "getCharPref"; G_Preferences.getterMap_[Ci.nsIPrefBranch.PREF_BOOL] = "getBoolPref"; G_Preferences.getterMap_[Ci.nsIPrefBranch.PREF_INT] = "getIntPref"; G_Preferences.prototype.__defineGetter__('prefs_', function() { var prefs; var prefSvc = Cc["@mozilla.org/preferences-service;1"] .getService(Ci.nsIPrefService); if (this.getDefaultBranch_) { prefs = prefSvc.getDefaultBranch(this.startPoint_); } else { prefs = prefSvc.getBranch(this.startPoint_); } // QI to prefs in case we want to add observers prefs.QueryInterface(Ci.nsIPrefBranchInternal); return prefs; }); /** * Stores a key/value in a user preference. Valid types for val are string, * boolean, and number. Complex values are not yet supported (but feel free to * add them!). */ G_Preferences.prototype.setPref = function(key, val) { var datatype = typeof(val); if (datatype == "number" && (val % 1 != 0)) { throw new Error("Cannot store non-integer numbers in preferences."); } var meth = G_Preferences.setterMap_[datatype]; if (!meth) { throw new Error("Pref datatype {" + datatype + "} not supported."); } return this.prefs_[meth](key, val); } /** * Retrieves a user preference. Valid types for the value are the same as for * setPref. If the preference is not found, opt_default will be returned * instead. */ G_Preferences.prototype.getPref = function(key, opt_default) { var type = this.prefs_.getPrefType(key); // zero means that the specified pref didn't exist if (type == Ci.nsIPrefBranch.PREF_INVALID) { return opt_default; } var meth = G_Preferences.getterMap_[type]; if (!meth) { throw new Error("Pref datatype {" + type + "} not supported."); } // If a pref has been cleared, it will have a valid type but won't // be gettable, so this will throw. try { return this.prefs_[meth](key); } catch(e) { return opt_default; } } /** * Set a boolean preference * * @param which Name of preference to set * @param value Boolean indicating value to set * * @deprecated Just use setPref. */ G_Preferences.prototype.setBoolPref = function(which, value) { return this.setPref(which, value); } /** * Get a boolean preference. WILL THROW IF PREFERENCE DOES NOT EXIST. * If you don't want this behavior, use getBoolPrefOrDefault. * * @param which Name of preference to get. * * @deprecated Just use getPref. */ G_Preferences.prototype.getBoolPref = function(which) { return this.prefs_.getBoolPref(which); } /** * Get a boolean preference or return some default value if it doesn't * exist. Note that the default doesn't have to be bool -- it could be * anything (e.g., you could pass in null and check if the return * value is === null to determine if the pref doesn't exist). * * @param which Name of preference to get. * @param def Value to return if the preference doesn't exist * @returns Boolean value of the pref if it exists, else def * * @deprecated Just use getPref. */ G_Preferences.prototype.getBoolPrefOrDefault = function(which, def) { return this.getPref(which, def); } /** * Get a boolean preference if it exists. If it doesn't, set its value * to a default and return the default. Note that the default will be * coherced to a bool if it is set, but not in the return value. * * @param which Name of preference to get. * @param def Value to set and return if the preference doesn't exist * @returns Boolean value of the pref if it exists, else def * * @deprecated Just use getPref. */ G_Preferences.prototype.getBoolPrefOrDefaultAndSet = function(which, def) { try { return this.prefs_.getBoolPref(which); } catch(e) { this.prefs_.setBoolPref(which, !!def); // The !! forces boolean conversion return def; } } /** * Delete a preference. * * @param which Name of preference to obliterate */ G_Preferences.prototype.clearPref = function(which) { try { // This throws if the pref doesn't exist, which is fine because a // non-existent pref is cleared this.prefs_.clearUserPref(which); } catch(e) {} } /** * Add an observer for a given pref. * * @param which String containing the pref to listen to * @param callback Function to be called when the pref changes. This * function will receive a single argument, a string * holding the preference name that changed */ G_Preferences.prototype.addObserver = function(which, callback) { var observer = new G_PreferenceObserver(callback); // Need to store the observer we create so we can eventually unregister it if (!this.observers_[which]) this.observers_[which] = new G_ObjectSafeMap(); this.observers_[which].insert(callback, observer); this.prefs_.addObserver(which, observer, false /* strong reference */); } /** * Remove an observer for a given pref. * * @param which String containing the pref to stop listening to * @param callback Function to remove as an observer */ G_Preferences.prototype.removeObserver = function(which, callback) { var observer = this.observers_[which].find(callback); G_Assert(this, !!observer, "Tried to unregister a nonexistant observer"); this.prefs_.removeObserver(which, observer); this.observers_[which].erase(callback); } /** * Helper class that knows how to observe preference changes and * invoke a callback when they do * * @constructor * @param callback Function to call when the preference changes */ function G_PreferenceObserver(callback) { this.debugZone = "prefobserver"; this.callback_ = callback; } /** * Invoked by the pref system when a preference changes. Passes the * message along to the callback. * * @param subject The nsIPrefBranch that changed * @param topic String "nsPref:changed" (aka * NS_PREFBRANCH_PREFCHANGE_OBSERVER_ID -- but where does it * live???) * @param data Name of the pref that changed */ G_PreferenceObserver.prototype.observe = function(subject, topic, data) { G_Debug(this, "Observed pref change: " + data); this.callback_(data); } /** * XPCOM cruft * * @param iid Interface id of the interface the caller wants */ G_PreferenceObserver.prototype.QueryInterface = function(iid) { var Ci = Ci; if (iid.equals(Ci.nsISupports) || iid.equals(Ci.nsIObserves) || iid.equals(Ci.nsISupportsWeakReference)) return this; throw Components.results.NS_ERROR_NO_INTERFACE; } /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Google Safe Browsing. * * The Initial Developer of the Original Code is Google Inc. * Portions created by the Initial Developer are Copyright (C) 2006 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Aaron Boodman <aa@google.com> (original author) * Raphael Moll <raphael@google.com> * Fritz Schneider <fritz@google.com> * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ // Utilities for working with nsIFile and related interfaces. /** * Stub for an nsIFile wrapper which doesn't exist yet. Perhaps in the future * we could add functionality to nsILocalFile which would be useful to us here, * but for now, no need for such. This could be done by setting * __proto__ to an instance of nsIFile, for example. Neat. */ var G_File = {}; /** * Returns an nsIFile pointing to the user's home directory, or optionally, a * file inside that dir. */ G_File.getHomeFile = function(opt_file) { return this.getSpecialFile("Home", opt_file); } /** * Returns an nsIFile pointing to the current profile folder, or optionally, a * file inside that dir. */ G_File.getProfileFile = function(opt_file) { return this.getSpecialFile("ProfD", opt_file); } /** * returns an nsIFile pointing to the temporary dir, or optionally, a file * inside that dir. */ G_File.getTempFile = function(opt_file) { return this.getSpecialFile("TmpD", opt_file); } /** * Returns an nsIFile pointing to one of the special named directories defined * by Firefox, such as the user's home directory, the profile directory, etc. * * As a convenience, callers may specify the opt_file argument to get that file * within the special directory instead. * * http://lxr.mozilla.org/seamonkey/source/xpcom/io/nsDirectoryServiceDefs.h * http://kb.mozillazine.org/File_IO#Getting_special_files */ G_File.getSpecialFile = function(loc, opt_file) { var file = Cc["@mozilla.org/file/directory_service;1"] .getService(Ci.nsIProperties) .get(loc, Ci.nsILocalFile); if (opt_file) { file.append(opt_file); } return file; } /** * Creates and returns a pointer to a unique file in the temporary directory * with an optional base name. */ G_File.createUniqueTempFile = function(opt_baseName) { var baseName = (opt_baseName || (new Date().getTime())) + ".tmp"; var file = this.getSpecialFile("TmpD", baseName); file.createUnique(file.NORMAL_FILE_TYPE, 0644); return file; } /** * Creates and returns a pointer to a unique temporary directory, with * an optional base name. */ G_File.createUniqueTempDir = function(opt_baseName) { var baseName = (opt_baseName || (new Date().getTime())) + ".tmp"; var dir = this.getSpecialFile("TmpD", baseName); dir.createUnique(dir.DIRECTORY_TYPE, 0744); return dir; } /** * Static method to retrieve an nsIFile from a file:// URI. */ G_File.fromFileURI = function(uri) { // Ensure they use file:// url's: discourages platform-specific paths if (uri.indexOf("file://") != 0) throw new Error("File path must be a file:// URL"); var fileHandler = Cc["@mozilla.org/network/protocol;1?name=file"] .getService(Ci.nsIFileProtocolHandler); return fileHandler.getFileFromURLSpec(uri); } // IO Constants G_File.PR_RDONLY = 0x01; // read-only G_File.PR_WRONLY = 0x02; // write only G_File.PR_RDWR = 0x04; // reading and writing G_File.PR_CREATE_FILE = 0x08; // create if it doesn't exist G_File.PR_APPEND = 0x10; // file pntr reset to end prior to writes G_File.PR_TRUNCATE = 0x20; // file exists its length is set to zero G_File.PR_SYNC = 0x40; // writes wait for data to be physically written G_File.PR_EXCL = 0x80; // file does not exist ? created : no action // The character(s) to use for line-endings, which are platform-specific. // This doesn't work for mac os9, but I don't know of a good way to detect // OS9-ness from JS. G_File.__defineGetter__("LINE_END_CHAR", function() { var end_char = Cc["@mozilla.org/xre/app-info;1"] .getService(Ci.nsIXULRuntime) .OS == "WINNT" ? "\r\n" : "\n"; // Cache result G_File.__defineGetter__("LINE_END_CHAR", function() { return end_char; }); return end_char; }); /** * A class which can read a file incrementally or all at once. Parameter can be * either an nsIFile instance or a string file:// URI. * Note that this class is not compatible with non-ascii data. */ function G_FileReader(file) { this.file_ = isString(file) ? G_File.fromFileURI(file) : file; } /** * Utility method to read the entire contents of a file. Parameter can be either * an nsIFile instance or a string file:// URI. */ G_FileReader.readAll = function(file) { var reader = new G_FileReader(file); try { return reader.read(); } finally { reader.close(); } } /** * Read up to opt_maxBytes from the stream. If opt_maxBytes is not specified, * the entire file is read. */ G_FileReader.prototype.read = function(opt_maxBytes) { if (!this.stream_) { var fs = Cc["@mozilla.org/network/file-input-stream;1"] .createInstance(Ci.nsIFileInputStream); fs.init(this.file_, G_File.PR_RDONLY, 0444, 0); this.stream_ = Cc["@mozilla.org/scriptableinputstream;1"] .createInstance(Ci.nsIScriptableInputStream); this.stream_.init(fs); } if (typeof opt_maxBytes == "undefined") { opt_maxBytes = this.stream_.available(); } return this.stream_.read(opt_maxBytes); } /** * Close the stream. This step is required when reading is done. */ G_FileReader.prototype.close = function(opt_maxBytes) { if (this.stream_) { this.stream_.close(); this.stream_ = null; } } // TODO(anyone): Implement G_LineReader. The interface should be something like: // for (var line = null; line = reader.readLine();) { // // do something with line // } /** * Writes a file incrementally or all at once. * Note that this class is not compatible with non-ascii data. */ function G_FileWriter(file, opt_append) { this.file_ = typeof file == "string" ? G_File.fromFileURI(file) : file; this.append_ = !!opt_append; } /** * Helper to write to a file in one step. */ G_FileWriter.writeAll = function(file, data, opt_append) { var writer = new G_FileWriter(file, opt_append); try { return writer.write(data); } finally { writer.close(); return 0; } } /** * Write bytes out to the file. Returns the number of bytes written. */ G_FileWriter.prototype.write = function(data) { if (!this.stream_) { this.stream_ = Cc["@mozilla.org/network/file-output-stream;1"] .createInstance(Ci.nsIFileOutputStream); var flags = G_File.PR_WRONLY | G_File.PR_CREATE_FILE | (this.append_ ? G_File.PR_APPEND : G_File.PR_TRUNCATE); this.stream_.init(this.file_, flags, -1 /* default perms */, 0 /* no special behavior */); } return this.stream_.write(data, data.length); } /** * Writes bytes out to file followed by the approriate line-ending character for * the current platform. */ G_FileWriter.prototype.writeLine = function(data) { this.write(data + G_File.LINE_END_CHAR); } /** * Closes the file. This must becalled when writing is done. */ G_FileWriter.prototype.close = function() { if (this.stream_) { this.stream_.close(); this.stream_ = null; } } /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Google Safe Browsing. * * The Initial Developer of the Original Code is Google Inc. * Portions created by the Initial Developer are Copyright (C) 2006 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Fritz Schneider <fritz@google.com> (original author) * Annie Sullivan <sullivan@google.com> * Aaron Boodman <aa@google.com> * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ // Generic logging/debugging functionality that: // // (*) when disabled compiles to no-ops at worst (for calls to the service) // and to nothing at best (calls to G_Debug() and similar are compiled // away when you use a jscompiler that strips dead code) // // (*) has dynamically configurable/creatable debugging "zones" enabling // selective logging // // (*) hides its plumbing so that all calls in different zones are uniform, // so you can drop files using this library into other apps that use it // without any configuration // // (*) can be controlled programmatically or via preferences. The // preferences that control the service and its zones are under // the preference branch "safebrowsing-debug-service." // // (*) outputs function call traces when the "loggifier" zone is enabled // // (*) can write output to logfiles so that you can get a call trace // from someone who is having a problem // // Example: // // var G_GDEBUG = true // Enable this module // var G_debugService = new G_DebugService(); // in global context // // // You can use it with arbitrary primitive first arguement // G_Debug("myzone", "Yo yo yo"); // outputs: [myzone] Yo yo yo\n // // // But it's nice to use it with an object; it will probe for the zone name // function Obj() { // this.debugZone = "someobj"; // } // Obj.prototype.foo = function() { // G_Debug(this, "foo called"); // } // (new Obj).foo(); // outputs: [someobj] foo called\n // // G_debugService.loggifier.loggify(Obj.prototype); // enable call tracing // // // En/disable specific zones programmatically (you can also use preferences) // G_debugService.enableZone("somezone"); // G_debugService.disableZone("someotherzone"); // G_debugService.enableAllZones(); // // // We also have asserts and errors: // G_Error(this, "Some error occurred"); // will throw // G_Assert(this, (x > 3), "x not greater than three!"); // will throw // // See classes below for more methods. // // TODO add abililty to alert() instead of dump()? Should be easy. // TODO add code to set prefs when not found to the default value of a tristate // TODO add error level support // TODO add ability to turn off console output // // -------> TO START DEBUGGING: set G_GDEBUG to true // These are the functions code will typically call. Everything is // wrapped in if's so we can compile it away when G_GDEBUG is false. if (typeof G_GDEBUG == "undefined") { throw new Error("G_GDEBUG constant must be set before loading debug.js"); } /** * Write out a debugging message. * * @param who The thingy to convert into a zone name corresponding to the * zone to which this message belongs * @param msg Message to output */ function G_Debug(who, msg) { if (G_GDEBUG) { G_GetDebugZone(who).debug(msg); } } /** * Debugs loudly */ function G_DebugL(who, msg) { if (G_GDEBUG) { var zone = G_GetDebugZone(who); if (zone.zoneIsEnabled()) { G_debugService.dump( G_File.LINE_END_CHAR + "************************************************************" + G_File.LINE_END_CHAR); G_Debug(who, msg); G_debugService.dump( "************************************************************" + G_File.LINE_END_CHAR + G_File.LINE_END_CHAR); } } } /** * Write out a call tracing message * * @param who The thingy to convert into a zone name corresponding to the * zone to which this message belongs * @param msg Message to output */ function G_TraceCall(who, msg) { if (G_GDEBUG) { if (G_debugService.callTracingEnabled()) { G_debugService.dump(msg + G_File.LINE_END_CHAR); } } } /** * Write out an error (and throw) * * @param who The thingy to convert into a zone name corresponding to the * zone to which this message belongs * @param msg Message to output */ function G_Error(who, msg) { if (G_GDEBUG) { G_GetDebugZone(who).error(msg); } } /** * Assert something as true and signal an error if it's not * * @param who The thingy to convert into a zone name corresponding to the * zone to which this message belongs * @param condition Boolean condition to test * @param msg Message to output */ function G_Assert(who, condition, msg) { if (G_GDEBUG) { G_GetDebugZone(who).assert(condition, msg); } } /** * Assert two things are equal (as in ==). */ function G_AssertEqual(who, expected, actual, msg) { if (G_GDEBUG) { G_GetDebugZone(who).assert( expected == actual, msg + " Expected: {%s}, got: {%s}".subs(expected, actual)); } } /** * Helper function that takes input and returns the DebugZone * corresponding to it. * * @param who Arbitrary input that will be converted into a zone name. Most * likely an object that has .debugZone property, or a string. * @returns The DebugZone object corresponding to the input */ function G_GetDebugZone(who) { if (G_GDEBUG) { var zone = "?"; if (who && who.debugZone) { zone = who.debugZone; } else if (isString(who)) { zone = who; } return G_debugService.getZone(zone); } } // Classes that implement the functionality. /** * A debug "zone" is a string derived from arbitrary types (but * typically derived from another string or an object). All debugging * messages using a particular zone can be enabled or disabled * independent of other zones. This enables you to turn on/off logging * of particular objects or modules. This object implements a single * zone and the methods required to use it. * * @constructor * @param service Reference to the DebugService object we use for * registration * @param prefix String indicating the unique prefix we should use * when creating preferences to control this zone * @param zone String indicating the name of the zone */ function G_DebugZone(service, prefix, zone) { if (G_GDEBUG) { this.debugService_ = service; this.prefix_ = prefix; this.zone_ = zone; this.zoneEnabledPrefName_ = prefix + ".zone." + this.zone_; this.settings_ = new G_DebugSettings(); } } /** * @returns Boolean indicating if this zone is enabled */ G_DebugZone.prototype.zoneIsEnabled = function() { if (G_GDEBUG) { var explicit = this.settings_.getSetting(this.zoneEnabledPrefName_, null); if (explicit !== null) { return explicit; } else { return this.debugService_.allZonesEnabled(); } } } /** * Enable this logging zone */ G_DebugZone.prototype.enableZone = function() { if (G_GDEBUG) { this.settings_.setDefault(this.zoneEnabledPrefName_, true); } } /** * Disable this logging zone */ G_DebugZone.prototype.disableZone = function() { if (G_GDEBUG) { this.settings_.setDefault(this.zoneEnabledPrefName_, false); } } /** * Write a debugging message to this zone * * @param msg String of message to write */ G_DebugZone.prototype.debug = function(msg) { if (G_GDEBUG) { if (this.zoneIsEnabled()) { this.debugService_.dump("[%s] %s%s".subs(this.zone_, msg, G_File.LINE_END_CHAR)); } } } /** * Write an error to this zone and throw * * @param msg String of error to write */ G_DebugZone.prototype.error = function(msg) { if (G_GDEBUG) { this.debugService_.dump("[%s] %s%s".subs(this.zone_, msg, G_File.LINE_END_CHAR)); throw new Error(msg); debugger; } } /** * Assert something as true and error if it is not * * @param condition Boolean condition to test * @param msg String of message to write if is false */ G_DebugZone.prototype.assert = function(condition, msg) { if (G_GDEBUG) { if (condition !== true) { G_Error(this.zone_, "ASSERT FAILED: " + msg); } } } /** * The debug service handles auto-registration of zones, namespacing * the zones preferences, and various global settings such as whether * all zones are enabled. * * @constructor * @param opt_prefix Optional string indicating the unique prefix we should * use when creating preferences */ function G_DebugService(opt_prefix) { if (G_GDEBUG) { this.prefix_ = opt_prefix ? opt_prefix : "safebrowsing-debug-service"; this.consoleEnabledPrefName_ = this.prefix_ + ".alsologtoconsole"; this.allZonesEnabledPrefName_ = this.prefix_ + ".enableallzones"; this.callTracingEnabledPrefName_ = this.prefix_ + ".trace-function-calls"; this.logFileEnabledPrefName_ = this.prefix_ + ".logfileenabled"; this.logFileErrorLevelPrefName_ = this.prefix_ + ".logfile-errorlevel"; this.zones_ = {}; this.loggifier = new G_Loggifier(); this.settings_ = new G_DebugSettings(); // We observe the console service so that we can echo errors that get // reported there to the file log. Cc["@mozilla.org/consoleservice;1"] .getService(Ci.nsIConsoleService) .registerListener(this); } } // Error levels for reporting console messages to the log. G_DebugService.ERROR_LEVEL_INFO = "INFO"; G_DebugService.ERROR_LEVEL_WARNING = "WARNING"; G_DebugService.ERROR_LEVEL_EXCEPTION = "EXCEPTION"; /** * @returns Boolean indicating if we should send messages to the jsconsole */ G_DebugService.prototype.alsoDumpToConsole = function() { if (G_GDEBUG) { return this.settings_.getSetting(this.consoleEnabledPrefName_, false); } } /** * @returns whether to log output to a file as well as the console. */ G_DebugService.prototype.logFileIsEnabled = function() { if (G_GDEBUG) { return this.settings_.getSetting(this.logFileEnabledPrefName_, false); } } /** * Turns on file logging. dump() output will also go to the file specified by * setLogFile() */ G_DebugService.prototype.enableLogFile = function() { if (G_GDEBUG) { this.settings_.setDefault(this.logFileEnabledPrefName_, true); } } /** * Turns off file logging */ G_DebugService.prototype.disableLogFile = function() { if (G_GDEBUG) { this.settings_.setDefault(this.logFileEnabledPrefName_, false); } } /** * @returns an nsIFile instance pointing to the current log file location */ G_DebugService.prototype.getLogFile = function() { if (G_GDEBUG) { return this.logFile_; } } /** * Sets a new log file location */ G_DebugService.prototype.setLogFile = function(file) { if (G_GDEBUG) { this.logFile_ = file; } } /** * Enables sending messages to the jsconsole */ G_DebugService.prototype.enableDumpToConsole = function() { if (G_GDEBUG) { this.settings_.setDefault(this.consoleEnabledPrefName_, true); } } /** * Disables sending messages to the jsconsole */ G_DebugService.prototype.disableDumpToConsole = function() { if (G_GDEBUG) { this.settings_.setDefault(this.consoleEnabledPrefName_, false); } } /** * @param zone Name of the zone to get * @returns The DebugZone object corresopnding to input. If not such * zone exists, a new one is created and returned */ G_DebugService.prototype.getZone = function(zone) { if (G_GDEBUG) { if (!this.zones_[zone]) this.zones_[zone] = new G_DebugZone(this, this.prefix_, zone); return this.zones_[zone]; } } /** * @param zone Zone to enable debugging for */ G_DebugService.prototype.enableZone = function(zone) { if (G_GDEBUG) { var toEnable = this.getZone(zone); toEnable.enableZone(); } } /** * @param zone Zone to disable debugging for */ G_DebugService.prototype.disableZone = function(zone) { if (G_GDEBUG) { var toDisable = this.getZone(zone); toDisable.disableZone(); } } /** * @returns Boolean indicating whether debugging is enabled for all zones */ G_DebugService.prototype.allZonesEnabled = function() { if (G_GDEBUG) { return this.settings_.getSetting(this.allZonesEnabledPrefName_, false); } } /** * Enables all debugging zones */ G_DebugService.prototype.enableAllZones = function() { if (G_GDEBUG) { this.settings_.setDefault(this.allZonesEnabledPrefName_, true); } } /** * Disables all debugging zones */ G_DebugService.prototype.disableAllZones = function() { if (G_GDEBUG) { this.settings_.setDefault(this.allZonesEnabledPrefName_, false); } } /** * @returns Boolean indicating whether call tracing is enabled */ G_DebugService.prototype.callTracingEnabled = function() { if (G_GDEBUG) { return this.settings_.getSetting(this.callTracingEnabledPrefName_, false); } } /** * Enables call tracing */ G_DebugService.prototype.enableCallTracing = function() { if (G_GDEBUG) { this.settings_.setDefault(this.callTracingEnabledPrefName_, true); } } /** * Disables call tracing */ G_DebugService.prototype.disableCallTracing = function() { if (G_GDEBUG) { this.settings_.setDefault(this.callTracingEnabledPrefName_, false); } } /** * Gets the minimum error that will be reported to the log. */ G_DebugService.prototype.getLogFileErrorLevel = function() { if (G_GDEBUG) { var level = this.settings_.getSetting(this.logFileErrorLevelPrefName_, G_DebugService.ERROR_LEVEL_EXCEPTION); return level.toUpperCase(); } } /** * Sets the minimum error level that will be reported to the log. */ G_DebugService.prototype.setLogFileErrorLevel = function(level) { if (G_GDEBUG) { // normalize case just to make it slightly easier to not screw up. level = level.toUpperCase(); if (level != G_DebugService.ERROR_LEVEL_INFO && level != G_DebugService.ERROR_LEVEL_WARNING && level != G_DebugService.ERROR_LEVEL_EXCEPTION) { throw new Error("Invalid error level specified: {" + level + "}"); } this.settings_.setDefault(this.logFileErrorLevelPrefName_, level); } } /** * Internal dump() method * * @param msg String of message to dump */ G_DebugService.prototype.dump = function(msg) { if (G_GDEBUG) { dump(msg); if (this.alsoDumpToConsole()) { try { var console = Components.classes['@mozilla.org/consoleservice;1'] .getService(Components.interfaces.nsIConsoleService); console.logStringMessage(msg); } catch(e) { dump("G_DebugZone ERROR: COULD NOT DUMP TO CONSOLE" + G_File.LINE_END_CHAR); } } this.maybeDumpToFile(msg); } } /** * Writes the specified message to the log file, if file logging is enabled. */ G_DebugService.prototype.maybeDumpToFile = function(msg) { if (this.logFileIsEnabled() && this.logFile_) { if (!this.logWriter_) { this.logWriter_ = new G_FileWriter(this.logFile_, true); } this.logWriter_.write(msg); } } /** * Implements nsIConsoleListener.observe(). Gets called when an error message * gets reported to the console and sends it to the log file as well. */ G_DebugService.prototype.observe = function(consoleMessage) { if (G_GDEBUG) { var errorLevel = this.getLogFileErrorLevel(); // consoleMessage can be either nsIScriptError or nsIConsoleMessage. The // latter does not have things like line number, etc. So we special case // it first. if (!(consoleMessage instanceof Ci.nsIScriptError)) { // Only report these messages if the error level is INFO. if (errorLevel == G_DebugService.ERROR_LEVEL_INFO) { this.maybeDumpToFile(G_DebugService.ERROR_LEVEL_INFO + ": " + consoleMessage.message + G_File.LINE_END_CHAR); } return; } // We make a local copy of these fields because writing to it doesn't seem // to work. var flags = consoleMessage.flags; var sourceName = consoleMessage.sourceName; var lineNumber = consoleMessage.lineNumber; // Sometimes, a scripterror instance won't have any flags set. We // default to exception. if (!flags) { flags = Ci.nsIScriptError.exceptionFlag; } // Default the filename and line number if they aren't set. if (!sourceName) { sourceName = "<unknown>"; } if (!lineNumber) { lineNumber = "<unknown>"; } // Report the error in the log file. if (flags & Ci.nsIScriptError.warningFlag) { // Only report warnings if the error level is warning or better. if (errorLevel == G_DebugService.ERROR_LEVEL_WARNING || errorLevel == G_DebugService.ERROR_LEVEL_INFO) { this.reportScriptError_(consoleMessage.message, sourceName, lineNumber, G_DebugService.ERROR_LEVEL_WARNING); } } else if (flags & Ci.nsIScriptError.exceptionFlag) { // Always report exceptions. this.reportScriptError_(consoleMessage.message, sourceName, lineNumber, G_DebugService.ERROR_LEVEL_EXCEPTION); } } } /** * Private helper to report an nsIScriptError instance to the log/console. */ G_DebugService.prototype.reportScriptError_ = function(message, sourceName, lineNumber, label) { var message = ["", "------------------------------------------------------------", label + ": " + message, "location: " + sourceName + ", " + "line: " + lineNumber, "------------------------------------------------------------", "", ""].join(G_File.LINE_END_CHAR); dump(message); this.maybeDumpToFile(message); } /** * A class that instruments methods so they output a call trace, * including the values of their actual parameters and return value. * This code is mostly stolen from Aaron Boodman's original * implementation in clobber utils. * * Note that this class uses the "loggifier" debug zone, so you'll see * a complete call trace when that zone is enabled. * * @constructor */ function G_Loggifier() { if (G_GDEBUG) { // Careful not to loggify ourselves! this.mark_(this); } } /** * Marks an object as having been loggified. Loggification is not * idempotent :) * * @param obj Object to be marked */ G_Loggifier.prototype.mark_ = function(obj) { if (G_GDEBUG) { obj.__loggified_ = true; } } /** * @param obj Object to be examined * @returns Boolean indicating if the object has been loggified */ G_Loggifier.prototype.isLoggified = function(obj) { if (G_GDEBUG) { return !!obj.__loggified_; } } /** * Attempt to extract the class name from the constructor definition. * Assumes the object was created using new. * * @param constructor String containing the definition of a constructor, * for example what you'd get by examining obj.constructor * @returns Name of the constructor/object if it could be found, else "???" */ G_Loggifier.prototype.getFunctionName_ = function(constructor) { if (G_GDEBUG) { return constructor.name || "???"; } } /** * Wraps all the methods in an object so that call traces are * automatically outputted. * * @param obj Object to loggify. SHOULD BE THE PROTOTYPE OF A USER-DEFINED * object. You can get into trouble if you attempt to * loggify something that isn't, for example the Window. * * Any additional parameters are considered method names which should not be * loggified. * * Usage: * G_debugService.loggifier.loggify(MyClass.prototype, * "firstMethodNotToLog", * "secondMethodNotToLog", * ... etc ...); */ G_Loggifier.prototype.loggify = function(obj) { if (G_GDEBUG) { if (!G_debugService.callTracingEnabled()) { return; } if (typeof window != "undefined" && obj == window || this.isLoggified(obj)) // Don't go berserk! return; var zone = G_GetDebugZone(obj); if (!zone || !zone.zoneIsEnabled()) { return; } this.mark_(obj); // Helper function returns an instrumented version of // objName.meth, with "this" bound properly. (BTW, because we're // in a conditional here, functions will only be defined as // they're encountered during execution, so declare this helper // before using it.) function wrap(meth, objName, methName) { return function() { // First output the call along with actual parameters var args = new Array(arguments.length); var argsString = ""; for (var i = 0; i < args.length; i++) { args[i] = arguments[i]; argsString += (i == 0 ? "" : ", "); if (isFunction(args[i])) { argsString += "[function]"; } else { argsString += args[i]; } } G_TraceCall(this, "> " + objName + "." + methName + "(" + argsString + ")"); // Then run the function, capturing the return value and throws try { var retVal = meth.apply(this, arguments); var reportedRetVal = retVal; if (typeof reportedRetVal == "undefined") reportedRetVal = "void"; else if (reportedRetVal === "") reportedRetVal = "\"\" (empty string)"; } catch (e) { if (e && !e.__logged) { G_TraceCall(this, "Error: " + e.message + ". " + e.fileName + ": " + e.lineNumber); try { e.__logged = true; } catch (e2) { // Sometimes we can't add the __logged flag because it's an // XPC wrapper throw e; } } throw e; // Re-throw! } // And spit it out already G_TraceCall( this, "< " + objName + "." + methName + ": " + reportedRetVal); return retVal; }; }; var ignoreLookup = {}; if (arguments.length > 1) { for (var i = 1; i < arguments.length; i++) { ignoreLookup[arguments[i]] = true; } } // Wrap each method of obj for (var p in obj) { // Work around bug in Firefox. In ffox typeof RegExp is "function", // so make sure this really is a function. Bug as of FFox 1.5b2. if (typeof obj[p] == "function" && obj[p].call && !ignoreLookup[p]) { var objName = this.getFunctionName_(obj.constructor); obj[p] = wrap(obj[p], objName, p); } } } } /** * Simple abstraction around debug settings. The thing with debug settings is * that we want to be able to specify a default in the application's startup, * but have that default be overridable by the user via their prefs. * * To generalize this, we package up a dictionary of defaults with the * preferences tree. If a setting isn't in the preferences tree, then we grab it * from the defaults. */ function G_DebugSettings() { this.defaults_ = {}; this.prefs_ = new G_Preferences(); } /** * Returns the value of a settings, optionally defaulting to a given value if it * doesn't exist. If no default is specified, the default is |undefined|. */ G_DebugSettings.prototype.getSetting = function(name, opt_default) { var override = this.prefs_.getPref(name, null); if (override !== null) { return override; } else if (typeof this.defaults_[name] != "undefined") { return this.defaults_[name]; } else { return opt_default; } } /** * Sets the default value for a setting. If the user doesn't override it with a * preference, this is the value which will be returned by getSetting(). */ G_DebugSettings.prototype.setDefault = function(name, val) { this.defaults_[name] = val; } var G_debugService = new G_DebugService(); // Instantiate us! if (G_GDEBUG) { G_debugService.enableAllZones(); } /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Google Safe Browsing. * * The Initial Developer of the Original Code is Google Inc. * Portions created by the Initial Developer are Copyright (C) 2006 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Fritz Schneider <fritz@google.com> (original author) * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ // An Alarm fires a callback after a certain amount of time, or at // regular intervals. It's a convenient replacement for // setTimeout/Interval when you don't want to bind to a specific // window. // // The ConditionalAlarm is an Alarm that cancels itself if its callback // returns a value that type-converts to true. // // Example: // // function foo() { alert('hi'); }; // new G_Alarm(foo, 10*1000); // Fire foo in 10 seconds // new G_Alarm(foo, 10*1000, true /*repeat*/); // Fire foo every 10 seconds // new G_Alarm(foo, 10*1000, true, 7); // Fire foo every 10 seconds // // seven times // new G_ConditionalAlarm(foo, 1000, true); // Fire every sec until foo()==true // // // Fire foo every 10 seconds until foo returns true or until it fires seven // // times, whichever happens first. // new G_ConditionalAlarm(foo, 10*1000, true /*repeating*/, 7); // // TODO: maybe pass an isFinal flag to the callback if they opted to // set maxTimes and this is the last iteration? /** * Set an alarm to fire after a given amount of time, or at specific * intervals. * * @param callback Function to call when the alarm fires * @param delayMS Number indicating the length of the alarm period in ms * @param opt_repeating Boolean indicating whether this should fire * periodically * @param opt_maxTimes Number indicating a maximum number of times to * repeat (obviously only useful when opt_repeating==true) */ function G_Alarm(callback, delayMS, opt_repeating, opt_maxTimes) { this.debugZone = "alarm"; this.callback_ = callback; this.repeating_ = !!opt_repeating; this.timer_ = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); var type = opt_repeating ? this.timer_.TYPE_REPEATING_SLACK : this.timer_.TYPE_ONE_SHOT; this.maxTimes_ = opt_maxTimes ? opt_maxTimes : null; this.nTimes_ = 0; this.observerServiceObserver_ = new G_ObserverServiceObserver( 'xpcom-shutdown', BindToObject(this.cancel, this)); // Ask the timer to use nsITimerCallback (.notify()) when ready this.timer_.initWithCallback(this, delayMS, type); } /** * Cancel this timer */ G_Alarm.prototype.cancel = function() { if (!this.timer_) { return; } this.timer_.cancel(); // Break circular reference created between this.timer_ and the G_Alarm // instance (this) this.timer_ = null; this.callback_ = null; // We don't need the shutdown observer anymore this.observerServiceObserver_.unregister(); } /** * Invoked by the timer when it fires * * @param timer Reference to the nsITimer which fired (not currently * passed along) */ G_Alarm.prototype.notify = function(timer) { // fire callback and save results var ret = this.callback_(); // If they've given us a max number of times to fire, enforce it this.nTimes_++; if (this.repeating_ && typeof this.maxTimes_ == "number" && this.nTimes_ >= this.maxTimes_) { this.cancel(); } else if (!this.repeating_) { // Clear out the callback closure for TYPE_ONE_SHOT timers this.cancel(); } // We don't cancel/cleanup timers that repeat forever until either // xpcom-shutdown occurs or cancel() is called explicitly. return ret; } /** * XPCOM cruft */ G_Alarm.prototype.QueryInterface = function(iid) { if (iid.equals(Components.interfaces.nsISupports) || iid.equals(Components.interfaces.nsITimerCallback)) return this; throw Components.results.NS_ERROR_NO_INTERFACE; } /** * An alarm with the additional property that it cancels itself if its * callback returns true. * * For parameter documentation, see G_Alarm */ function G_ConditionalAlarm(callback, delayMS, opt_repeating, opt_maxTimes) { G_Alarm.call(this, callback, delayMS, opt_repeating, opt_maxTimes); this.debugZone = "conditionalalarm"; } G_ConditionalAlarm.inherits(G_Alarm); /** * Invoked by the timer when it fires * * @param timer Reference to the nsITimer which fired (not currently * passed along) */ G_ConditionalAlarm.prototype.notify = function(timer) { // Call G_Alarm::notify var rv = G_Alarm.prototype.notify.call(this, timer); if (this.repeating_ && rv) { G_Debug(this, "Callback of a repeating alarm returned true; cancelling."); this.cancel(); } } /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Google Safe Browsing. * * The Initial Developer of the Original Code is Google Inc. * Portions created by the Initial Developer are Copyright (C) 2006 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Fritz Schneider <fritz@google.com> (original author) * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ // Base64 en/decoding. Not much to say here except that we work with // decoded values in arrays of bytes. By "byte" I mean a number in [0, // 255]. /** * Base64 en/decoder. Useful in contexts that don't have atob/btoa, or * when you need a custom encoding function (e.g., websafe base64). * * @constructor */ function G_Base64() { this.byteToCharMap_ = {}; this.charToByteMap_ = {}; this.byteToCharMapWebSafe_ = {}; this.charToByteMapWebSafe_ = {}; this.init_(); } /** * Our default alphabet. Value 64 (=) is special; it means "nothing." */ G_Base64.ENCODED_VALS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/="; /** * Our websafe alphabet. Value 64 (=) is special; it means "nothing." */ G_Base64.ENCODED_VALS_WEBSAFE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789-_="; /** * We want quick mappings back and forth, so we precompute two maps. */ G_Base64.prototype.init_ = function() { for (var i = 0; i < G_Base64.ENCODED_VALS.length; i++) { this.byteToCharMap_[i] = G_Base64.ENCODED_VALS.charAt(i); this.charToByteMap_[this.byteToCharMap_[i]] = i; this.byteToCharMapWebSafe_[i] = G_Base64.ENCODED_VALS_WEBSAFE.charAt(i); this.charToByteMapWebSafe_[this.byteToCharMapWebSafe_[i]] = i; } } /** * Base64-encode an array of bytes. * * @param input An array of bytes (numbers with value in [0, 255]) to encode * * @param opt_webSafe Boolean indicating we should use the alternative alphabet * * @returns String containing the base64 encoding */ G_Base64.prototype.encodeByteArray = function(input, opt_webSafe) { if (!(input instanceof Array)) throw new Error("encodeByteArray takes an array as a parameter"); var byteToCharMap = opt_webSafe ? this.byteToCharMapWebSafe_ : this.byteToCharMap_; var output = []; var i = 0; while (i < input.length) { var byte1 = input[i]; var haveByte2 = i + 1 < input.length; var byte2 = haveByte2 ? input[i + 1] : 0; var haveByte3 = i + 2 < input.length; var byte3 = haveByte3 ? input[i + 2] : 0; var outByte1 = byte1 >> 2; var outByte2 = ((byte1 & 0x03) << 4) | (byte2 >> 4); var outByte3 = ((byte2 & 0x0F) << 2) | (byte3 >> 6); var outByte4 = byte3 & 0x3F; if (!haveByte3) { outByte4 = 64; if (!haveByte2) outByte3 = 64; } output.push(byteToCharMap[outByte1]); output.push(byteToCharMap[outByte2]); output.push(byteToCharMap[outByte3]); output.push(byteToCharMap[outByte4]); i += 3; } return output.join(""); } /** * Base64-decode a string. * * @param input String to decode * * @param opt_webSafe Boolean indicating we should use the alternative alphabet * * @returns Array of bytes representing the decoded value. */ G_Base64.prototype.decodeString = function(input, opt_webSafe) { if (input.length % 4) throw new Error("Length of b64-encoded data must be zero mod four"); var charToByteMap = opt_webSafe ? this.charToByteMapWebSafe_ : this.charToByteMap_; var output = []; var i = 0; while (i < input.length) { var byte1 = charToByteMap[input.charAt(i)]; var byte2 = charToByteMap[input.charAt(i + 1)]; var byte3 = charToByteMap[input.charAt(i + 2)]; var byte4 = charToByteMap[input.charAt(i + 3)]; if (byte1 === undefined || byte2 === undefined || byte3 === undefined || byte4 === undefined) throw new Error("String contains characters not in our alphabet: " + input); var outByte1 = (byte1 << 2) | (byte2 >> 4); output.push(outByte1); if (byte3 != 64) { var outByte2 = ((byte2 << 4) & 0xF0) | (byte3 >> 2); output.push(outByte2); if (byte4 != 64) { var outByte3 = ((byte3 << 6) & 0xC0) | byte4; output.push(outByte3); } } i += 4; } return output; } /** * Helper function that turns a string into an array of numbers. * * @param str String to arrify * * @returns Array holding numbers corresponding to the UCS character codes * of each character in str */ G_Base64.prototype.arrayifyString = function(str) { var output = []; for (var i = 0; i < str.length; i++) output.push(str.charCodeAt(i)); return output; } /** * Helper function that turns an array of numbers into the string * given by the concatenation of the characters to which the numbesr * correspond (got that?). * * @param array Array of numbers representing characters * * @returns Stringification of the array */ G_Base64.prototype.stringifyArray = function(array) { var output = []; for (var i = 0; i < array.length; i++) output[i] = String.fromCharCode(array[i]); return output.join(""); } /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Google Safe Browsing. * * The Initial Developer of the Original Code is Google Inc. * Portions created by the Initial Developer are Copyright (C) 2006 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Fritz Schneider <fritz@google.com> (original author) * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ // A very thin wrapper around nsICryptoHash. It's not strictly // necessary, but makes the code a bit cleaner and gives us the // opportunity to verify that our implementations give the results that // we expect, for example if we have to interoperate with a server. // // The digest* methods reset the state of the hasher, so it's // necessary to call init() explicitly after them. // // Works only in Firefox 1.5+. // // IMPORTANT NOTE: Due to https://bugzilla.mozilla.org/show_bug.cgi?id=321024 // you cannot use the cryptohasher before app-startup. The symptom of doing // so is a segfault in NSS. /** * Instantiate a new hasher. You must explicitly call init() before use! */ function G_CryptoHasher() { this.debugZone = "cryptohasher"; this.decoder_ = new G_Base64(); this.hasher_ = null; } G_CryptoHasher.algorithms = { MD2: Ci.nsICryptoHash.MD2, MD5: Ci.nsICryptoHash.MD5, SHA1: Ci.nsICryptoHash.SHA1, SHA256: Ci.nsICryptoHash.SHA256, SHA384: Ci.nsICryptoHash.SHA384, SHA512: Ci.nsICryptoHash.SHA512, }; /** * Initialize the hasher. This function must be called after every call * to one of the digest* methods. * * @param algorithm Constant from G_CryptoHasher.algorithms specifying the * algorithm this hasher will use */ G_CryptoHasher.prototype.init = function(algorithm) { var validAlgorithm = false; for (var alg in G_CryptoHasher.algorithms) if (algorithm == G_CryptoHasher.algorithms[alg]) validAlgorithm = true; if (!validAlgorithm) throw new Error("Invalid algorithm: " + algorithm); this.hasher_ = Cc["@mozilla.org/security/hash;1"] .createInstance(Ci.nsICryptoHash); this.hasher_.init(algorithm); } /** * Update the hash's internal state with input given in a string. Can be * called multiple times for incrementeal hash updates. Note that this function * is slllloooowww since it uses the a javascript implementation to convert the * string to an array. If you need something faster, use updateFromStream() with * an XPCOM stream. * * @param input String containing data to hash. */ G_CryptoHasher.prototype.updateFromString = function(input) { if (!this.hasher_) throw new Error("You must initialize the hasher first!"); this.hasher_.update(this.decoder_.arrayifyString(input), input.length); } /** * Update the hash's internal state with input given in an array. Can be * called multiple times for incremental hash updates. * * @param input Array containing data to hash. */ G_CryptoHasher.prototype.updateFromArray = function(input) { if (!this.hasher_) throw new Error("You must initialize the hasher first!"); this.hasher_.update(input, input.length); } /** * Update the hash's internal state with input given in a stream. Can be * called multiple times from incremental hash updates. */ G_CryptoHasher.prototype.updateFromStream = function(stream) { if (!this.hasher_) throw new Error("You must initialize the hasher first!"); this.hasher_.updateFromStream(stream, stream.available()); } /** * @returns The hash value as a string (sequence of 8-bit values) */ G_CryptoHasher.prototype.digestRaw = function() { var digest = this.hasher_.finish(false /* not b64 encoded */); this.hasher_ = null; return digest; } /** * @returns The hash value as a base64-encoded string */ G_CryptoHasher.prototype.digestBase64 = function() { var digest = this.hasher_.finish(true /* b64 encoded */); this.hasher_ = null; return digest; } /** * @returns The hash value as a hex-encoded string */ G_CryptoHasher.prototype.digestHex = function() { var raw = this.digestRaw(); return this.toHex_(raw); } /** * Converts a sequence of values to a hex-encoded string. The input is a * a string, so you can stick 16-bit values in each character. * * @param str String to conver to hex. (Often this is just a sequence of * 16-bit values) * * @returns String containing the hex representation of the input */ G_CryptoHasher.prototype.toHex_ = function(str) { var hexchars = '0123456789ABCDEF'; var hexrep = new Array(str.length * 2); for (var i = 0; i < str.length; ++i) { hexrep[i * 2] = hexchars.charAt((str.charCodeAt(i) >> 4) & 15); hexrep[i * 2 + 1] = hexchars.charAt(str.charCodeAt(i) & 15); } return hexrep.join(''); } /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Google Safe Browsing. * * The Initial Developer of the Original Code is Google Inc. * Portions created by the Initial Developer are Copyright (C) 2006 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Aaron Boodman <aa@google.com> (original author) * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ // Firefox-specific additions to lib/js/lang.js. /** * The always-useful alert. */ function alert(msg, opt_title) { opt_title |= "message"; Cc["@mozilla.org/embedcomp/prompt-service;1"] .getService(Ci.nsIPromptService) .alert(null, opt_title, msg.toString()); } /** * The instanceof operator cannot be used on a pure js object to determine if * it implements a certain xpcom interface. The QueryInterface method can, but * it throws an error which makes things more complex. */ function jsInstanceOf(obj, iid) { try { obj.QueryInterface(iid); return true; } catch (e) { if (e == Components.results.NS_ERROR_NO_INTERFACE) { return false; } else { throw e; } } } /** * Unbelievably, Function inheritence is broken in chrome in Firefox * (still as of FFox 1.5b1). Hence if you're working in an extension * and not using the subscriptloader, you can't use the method * above. Instead, use this global function that does roughly the same * thing. * *************************************************************************** * NOTE THE REVERSED ORDER OF FUNCTION AND OBJECT REFERENCES AS bind() * *************************************************************************** * * // Example to bind foo.bar(): * var bound = BindToObject(bar, foo, "arg1", "arg2"); * bound("arg3", "arg4"); * * @param func {string} Reference to the function to be bound * * @param obj {object} Specifies the object which |this| should point to * when the function is run. If the value is null or undefined, it will default * to the global object. * * @param opt_{...} Dummy optional arguments to make a jscompiler happy * * @returns {function} A partially-applied form of the speficied function. */ function BindToObject(func, obj, opt_A, opt_B, opt_C, opt_D, opt_E, opt_F) { // This is the sick product of Aaron's mind. Not for the feint of heart. var args = Array.prototype.splice.call(arguments, 1, arguments.length); return Function.prototype.bind.apply(func, args); } /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Google Safe Browsing. * * The Initial Developer of the Original Code is Google Inc. * Portions created by the Initial Developer are Copyright (C) 2006 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Fritz Schneider <fritz@google.com> (original author) * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ // ObjectSafeMap is, shockingly, a Map with which it is safe to use // objects as keys. It currently uses parallel arrays for storage, // rendering it inefficient (linear) for large maps. We can always // swap out the implementation if this becomes a problem. Note that // this class uses strict equality to determine equivalent keys. // // Interface: // // insert(key, value) // erase(key) // Returns true if key erased, false if not found // find(key) // Returns undefined if key not found // replace(otherMap) // Clones otherMap, replacing the current map // forEach(func) // size() // Returns number of items in the map // // TODO: should probably make it iterable by implementing getList(); /** * Create a new ObjectSafeMap. * * @param opt_name A string used to name the map * * @constructor */ function G_ObjectSafeMap(opt_name) { this.debugZone = "objectsafemap"; this.name_ = opt_name ? opt_name : "noname"; this.keys_ = []; this.values_ = []; } /** * Helper function to return the index of a key. * * @param key An key to find * * @returns Index in the keys array where the key is found, -1 if not */ G_ObjectSafeMap.prototype.indexOfKey_ = function(key) { for (var i = 0; i < this.keys_.length; i++) if (this.keys_[i] === key) return i; return -1; } /** * Add an item * * @param key An key to add (overwrites previous key) * * @param value The value to store at that key */ G_ObjectSafeMap.prototype.insert = function(key, value) { if (key === null) throw new Error("Can't use null as a key"); if (value === undefined) throw new Error("Can't store undefined values in this map"); var i = this.indexOfKey_(key); if (i == -1) { this.keys_.push(key); this.values_.push(value); } else { this.keys_[i] = key; this.values_[i] = value; } G_Assert(this, this.keys_.length == this.values_.length, "Different number of keys than values!"); } /** * Remove a key from the map * * @param key The key to remove * * @returns Boolean indicating if the key was removed */ G_ObjectSafeMap.prototype.erase = function(key) { var keyLocation = this.indexOfKey_(key); var keyFound = keyLocation != -1; if (keyFound) { this.keys_.splice(keyLocation, 1); this.values_.splice(keyLocation, 1); } G_Assert(this, this.keys_.length == this.values_.length, "Different number of keys than values!"); return keyFound; } /** * Look up a key in the map * * @param key The key to look up * * @returns The value at that key or undefined if it doesn't exist */ G_ObjectSafeMap.prototype.find = function(key) { var keyLocation = this.indexOfKey_(key); return keyLocation == -1 ? undefined : this.values_[keyLocation]; } /** * Replace one map with the content of another * * @param map input map that needs to be merged into our map */ G_ObjectSafeMap.prototype.replace = function(other) { this.keys_ = []; this.values_ = []; for (var i = 0; i < other.keys_.length; i++) { this.keys_.push(other.keys_[i]); this.values_.push(other.values_[i]); } G_Assert(this, this.keys_.length == this.values_.length, "Different number of keys than values!"); } /** * Apply a function to each of the key value pairs. * * @param func Function to apply to the map's key value pairs */ G_ObjectSafeMap.prototype.forEach = function(func) { if (typeof func != "function") throw new Error("argument to forEach is not a function, it's a(n) " + typeof func); for (var i = 0; i < this.keys_.length; i++) func(this.keys_[i], this.values_[i]); } /** * @returns The number of keys in the map */ G_ObjectSafeMap.prototype.size = function() { return this.keys_.length; } /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Google Safe Browsing. * * The Initial Developer of the Original Code is Google Inc. * Portions created by the Initial Developer are Copyright (C) 2006 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Fritz Schneider <fritz@google.com> (original author) * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ // A couple of classes to simplify creating observers. // // // Example1: // // function doSomething() { ... } // var observer = new G_ObserverWrapper(topic, doSomething); // someObj.addObserver(topic, observer); // // // Example2: // // function doSomething() { ... } // new G_ObserverServiceObserver("profile-after-change", // doSomething, // true /* run only once */); /** * This class abstracts the admittedly simple boilerplate required of * an nsIObserver. It saves you the trouble of implementing the * indirection of your own observe() function. * * @param topic String containing the topic the observer will filter for * * @param observeFunction Reference to the function to call when the * observer fires * * @constructor */ function G_ObserverWrapper(topic, observeFunction) { this.debugZone = "observer"; this.topic_ = topic; this.observeFunction_ = observeFunction; } /** * XPCOM */ G_ObserverWrapper.prototype.QueryInterface = function(iid) { if (iid.equals(Ci.nsISupports) || iid.equals(Ci.nsIObserver)) return this; throw Components.results.NS_ERROR_NO_INTERFACE; } /** * Invoked by the thingy being observed */ G_ObserverWrapper.prototype.observe = function(subject, topic, data) { if (topic == this.topic_) this.observeFunction_(subject, topic, data); } /** * This class abstracts the admittedly simple boilerplate required of * observing an observerservice topic. It implements the indirection * required, and automatically registers to hear the topic. * * @param topic String containing the topic the observer will filter for * * @param observeFunction Reference to the function to call when the * observer fires * * @param opt_onlyOnce Boolean indicating if the observer should unregister * after it has fired * * @constructor */ function G_ObserverServiceObserver(topic, observeFunction, opt_onlyOnce) { this.debugZone = "observerserviceobserver"; this.topic_ = topic; this.observeFunction_ = observeFunction; this.onlyOnce_ = !!opt_onlyOnce; this.observer_ = new G_ObserverWrapper(this.topic_, BindToObject(this.observe_, this)); this.observerService_ = Cc["@mozilla.org/observer-service;1"] .getService(Ci.nsIObserverService); this.observerService_.addObserver(this.observer_, this.topic_, false); } /** * Unregister the observer from the observerservice */ G_ObserverServiceObserver.prototype.unregister = function() { this.observerService_.removeObserver(this.observer_, this.topic_); this.observerService_ = null; } /** * Invoked by the observerservice */ G_ObserverServiceObserver.prototype.observe_ = function(subject, topic, data) { this.observeFunction_(subject, topic, data); if (this.onlyOnce_) this.unregister(); } /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Google Safe Browsing. * * The Initial Developer of the Original Code is Google Inc. * Portions created by the Initial Developer are Copyright (C) 2006 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Fritz Schneider <fritz@google.com> (original author) * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ // A helper class that knows how to parse from and serialize to // protocol4. This is a simple, historical format used by some Google // interfaces, for example the Toolbar (i.e., ancient services). // // Protocol4 consists of a newline-separated sequence of name/value // pairs (strings). Each line consists of the name, the value length, // and the value itself, all separated by colons. Example: // // foo:6:barbaz\n // fritz:33:issickofdynamicallytypedlanguages\n /** * This class knows how to serialize/deserialize maps to/from their * protocol4 representation. * * @constructor */ function G_Protocol4Parser() { this.debugZone = "protocol4"; this.protocol4RegExp_ = new RegExp("([^:]+):\\d+:(.*)$"); this.newlineRegExp_ = new RegExp("(\\r)?\\n"); } /** * Create a map from a protocol4 string. Silently skips invalid lines. * * @param text String holding the protocol4 representation * * @returns Object as an associative array with keys and values * given in text. The empty object is returned if none * are parsed. */ G_Protocol4Parser.prototype.parse = function(text) { var response = {}; if (!text) return response; // Responses are protocol4: (repeated) name:numcontentbytes:content\n var lines = text.split(this.newlineRegExp_); for (var i = 0; i < lines.length; i++) if (this.protocol4RegExp_.exec(lines[i])) response[RegExp.$1] = RegExp.$2; return response; } /** * Create a protocol4 string from a map (object). Throws an error on * an invalid input. * * @param map Object as an associative array with keys and values * given as strings. * * @returns text String holding the protocol4 representation */ G_Protocol4Parser.prototype.serialize = function(map) { if (typeof map != "object") throw new Error("map must be an object"); var text = ""; for (var key in map) { if (typeof map[key] != "string") throw new Error("Keys and values must be strings"); text += key + ":" + map[key].length + ":" + map[key] + "\n"; } return text; } //@line 59 "/cygdrive/c/builds/tinderbox/Fx-Mozilla1.8-release/WINNT_5.2_Depend/mozilla/toolkit/components/url-classifier/src/nsUrlClassifierLib.js" /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Google Safe Browsing. * * The Initial Developer of the Original Code is Google Inc. * Portions created by the Initial Developer are Copyright (C) 2006 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Fritz Schneider <fritz@google.com> (original author) * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ // TODO: We don't use this class very much. Try to use native nsIFile instead // and remove this file. /** * A simple helper class that enables us to get or create the * directory in which our app will store stuff. */ function PROT_ApplicationDirectory() { this.debugZone = "appdir"; this.appDir_ = G_File.getProfileFile(); G_Debug(this, "Application directory is " + this.appDir_.path); } /** * @returns Boolean indicating if the directory exists */ PROT_ApplicationDirectory.prototype.exists = function() { return this.appDir_.exists() && this.appDir_.isDirectory(); } /** * Creates the directory */ PROT_ApplicationDirectory.prototype.create = function() { G_Debug(this, "Creating app directory: " + this.appDir_.path); try { this.appDir_.create(Ci.nsIFile.DIRECTORY_TYPE, 0700); } catch(e) { G_Error(this, this.appDir_.path + " couldn't be created."); } } /** * @returns The nsIFile interface of the directory */ PROT_ApplicationDirectory.prototype.getAppDirFileInterface = function() { return this.appDir_; } /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Google Safe Browsing. * * The Initial Developer of the Original Code is Google Inc. * Portions created by the Initial Developer are Copyright (C) 2006 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Tony Chang <tc@google.com> (original author) * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ // This implements logic for stopping requests if the server starts to return // too many errors. If we get MAX_ERRORS errors in ERROR_PERIOD minutes, we // back off for TIMEOUT_INCREMENT minutes. If we get another error // immediately after we restart, we double the timeout and add // TIMEOUT_INCREMENT minutes, etc. // // This is similar to the logic used by the search suggestion service. // HTTP responses that count as an error. We also include any 5xx response // as an error. const HTTP_FOUND = 302; const HTTP_SEE_OTHER = 303; const HTTP_TEMPORARY_REDIRECT = 307; /** * @param maxErrors Number the number of errors needed to trigger backoff * @param errorPeriod Number time (ms) in which maxErros have to occur to * trigger the backoff behavior * @param timeoutIncrement Number time (ms) the starting timeout period * we double this time for consecutive errors * @param maxTimeout Number time (ms) maximum timeout period */ function RequestBackoff(maxErrors, errorPeriod, timeoutIncrement, maxTimeout) { this.MAX_ERRORS_ = maxErrors; this.ERROR_PERIOD_ = errorPeriod; this.TIMEOUT_INCREMENT_ = timeoutIncrement; this.MAX_TIMEOUT_ = maxTimeout; // Queue of ints keeping the time of errors. this.errorTimes_ = []; this.errorTimeout_ = 0; this.nextRequestTime_ = 0; this.backoffTriggered_ = false; } /** * Reset the object for reuse. */ RequestBackoff.prototype.reset = function() { this.errorTimes_ = []; this.errorTimeout_ = 0; this.nextRequestTime_ = 0; this.backoffTriggered_ = false; } /** * Check to see if we can make a request. */ RequestBackoff.prototype.canMakeRequest = function() { return Date.now() > this.nextRequestTime_; } /** * Notify this object of the last server response. If it's an error, */ RequestBackoff.prototype.noteServerResponse = function(status) { if (this.isErrorStatus_(status)) { var now = Date.now(); this.errorTimes_.push(now); // We only care about keeping track of MAX_ERRORS if (this.errorTimes_.length > this.MAX_ERRORS_) this.errorTimes_.shift(); // See if we hit the backoff case // This either means we hit MAX_ERRORS in ERROR_PERIOD // *or* we were already in a backoff state, in which case we // increase our timeout. if ((this.errorTimes_.length == this.MAX_ERRORS_ && now - this.errorTimes_[0] < this.ERROR_PERIOD_) || this.backoffTriggered_) { this.errorTimeout_ = (this.errorTimeout_ * 2) + this.TIMEOUT_INCREMENT_; this.errorTimeout_ = Math.min(this.errorTimeout_, this.MAX_TIMEOUT_); this.nextRequestTime_ = now + this.errorTimeout_; this.backoffTriggered_ = true; } } else { // Reset error timeout, allow requests to go through, and switch out // of backoff state. this.errorTimeout_ = 0; this.nextRequestTime_ = 0; this.backoffTriggered_ = false; } } /** * We consider 302, 303, 307, and 5xx http responses to be errors. * @param status Number http status * @return Boolean true if we consider this http status an error */ RequestBackoff.prototype.isErrorStatus_ = function(status) { return ((500 <= status && status <= 599) || HTTP_FOUND == status || HTTP_SEE_OTHER == status || HTTP_TEMPORARY_REDIRECT == status); } /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Google Safe Browsing. * * The Initial Developer of the Original Code is Google Inc. * Portions created by the Initial Developer are Copyright (C) 2006 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Fritz Schneider <fritz@google.com> (original author) * Monica Chew <mmc@google.com> * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ // This file implements our query param encryption. You hand it a set // of query params, and it will hand you a set of (maybe) encrypted // query params back. It takes the query params you give it, // encodes and encrypts them into a encrypted query param, and adds // the extra query params the server will need to decrypt them // (e.g., the version of encryption and the decryption key). // // The key manager provides the keys we need; this class just focuses // on encrypting query params. See the url crypto key manager for // details of our protocol, but essentially encryption is // RC4_key(input) with key == MD5(K_C || nonce) where nonce is a // 32-bit integer appended big-endian and K_C is the client's key. // // If for some reason we don't have an encryption key, encrypting is the // identity function. /** * This class knows how to encrypt query parameters that will be * understood by the lookupserver. * * @constructor */ function PROT_UrlCrypto() { this.debugZone = "urlcrypto"; this.hasher_ = new G_CryptoHasher(); this.base64_ = new G_Base64(); this.streamCipher_ = Cc["@mozilla.org/security/streamcipher;1"] .createInstance(Ci.nsIStreamCipher); if (!this.manager_) { // Create a UrlCryptoKeyManager to reads keys from profile directory if // one doesn't already exist. UrlCryptoKeyManager puts a reference to // itself on PROT_UrlCrypto.prototype (this also prevents garbage // collection). new PROT_UrlCryptoKeyManager(); } // Convenience properties this.VERSION = PROT_UrlCrypto.VERSION; this.RC4_DISCARD_BYTES = PROT_UrlCrypto.RC4_DISCARD_BYTES; this.VERSION_QUERY_PARAM_NAME = PROT_UrlCrypto.QPS.VERSION_QUERY_PARAM_NAME; this.ENCRYPTED_PARAMS_PARAM_NAME = PROT_UrlCrypto.QPS.ENCRYPTED_PARAMS_PARAM_NAME; this.COUNT_QUERY_PARAM_NAME = PROT_UrlCrypto.QPS.COUNT_QUERY_PARAM_NAME; this.WRAPPEDKEY_QUERY_PARAM_NAME = PROT_UrlCrypto.QPS.WRAPPEDKEY_QUERY_PARAM_NAME; // Properties for computing macs this.macer_ = new G_CryptoHasher(); // don't use hasher_ this.macInitialized_ = false; // Separator to prevent leakage between key and data when computing mac this.separator_ = ":coolgoog:"; this.separatorArray_ = this.base64_.arrayifyString(this.separator_); } // The version of encryption we implement PROT_UrlCrypto.VERSION = "1"; PROT_UrlCrypto.RC4_DISCARD_BYTES = 1600; // The query params are we going to send to let the server know what is // encrypted, and how PROT_UrlCrypto.QPS = {}; PROT_UrlCrypto.QPS.VERSION_QUERY_PARAM_NAME = "encver"; PROT_UrlCrypto.QPS.ENCRYPTED_PARAMS_PARAM_NAME = "encparams"; PROT_UrlCrypto.QPS.COUNT_QUERY_PARAM_NAME = "nonce"; PROT_UrlCrypto.QPS.WRAPPEDKEY_QUERY_PARAM_NAME = "wrkey"; /** * @returns Reference to the keymanager (if one exists), else undefined */ PROT_UrlCrypto.prototype.getManager = function() { return this.manager_; } /** * Helper method that takes a map of query params (param name -> * value) and turns them into a query string. Note that it encodes * the values as it writes the string. * * @param params Object (map) of query names to values. Values should * not be uriencoded. * * @returns String of query params from the map. Values will be uri * encoded */ PROT_UrlCrypto.prototype.appendParams_ = function(params) { var queryString = ""; for (var param in params) queryString += "&" + param + "=" + encodeURIComponent(params[param]); return queryString; } /** * Encrypt a set of query params if we can. If we can, we return a new * set of query params that should be added to a query string. The set * of query params WILL BE different than the input query params if we * can encrypt (e.g., there will be extra query params with meta- * information such as the version of encryption we're using). If we * can't encrypt, we just return the query params we're passed. * * @param params Object (map) of query param names to values. Values should * not be uriencoded. * * @returns Object (map) of query param names to values. Values are NOT * uriencoded; the caller should encode them as it writes them * to a proper query string. */ PROT_UrlCrypto.prototype.maybeCryptParams = function(params) { if (!this.manager_) throw new Error("Need a key manager for UrlCrypto"); if (typeof params != "object") throw new Error("params is an associative array of name/value params"); var clientKeyArray = this.manager_.getClientKeyArray(); var wrappedKey = this.manager_.getWrappedKey(); // No keys? Can't encrypt. Damn. if (!clientKeyArray || !wrappedKey) { G_Debug(this, "No key; can't encrypt query params"); return params; } // Serialize query params to a query string that we will then // encrypt and place in a special query param the front-end knows is // encrypted. var queryString = this.appendParams_(params); // Nonce, really. We want 32 bits; make it so. var counter = this.getCount_(); counter = counter & 0xFFFFFFFF; var encrypted = this.encryptV1(clientKeyArray, this.VERSION, counter, queryString); params = {}; params[this.VERSION_QUERY_PARAM_NAME] = this.VERSION; params[this.COUNT_QUERY_PARAM_NAME] = counter; params[this.WRAPPEDKEY_QUERY_PARAM_NAME] = wrappedKey; params[this.ENCRYPTED_PARAMS_PARAM_NAME] = encrypted; return params; } /** * Encrypts text and returns a base64 string of the results. * * This method runs in about ~2ms on a 2Ghz P4. (Turn debugging off if * you see it much slower). * * @param clientKeyArray Array of bytes (numbers in [0,255]) composing K_C * * @param version String indicating the version of encryption we should use. * * @param counter Number that acts as a nonce for this encryption * * @param text String to be encrypted * * @returns String containing the websafe base64-encoded ciphertext */ PROT_UrlCrypto.prototype.encryptV1 = function(clientKeyArray, version, counter, text) { // We're a version1 encrypter, after all if (version != "1") throw new Error("Unknown encryption version"); var key = this.deriveEncryptionKey(clientKeyArray, counter); this.streamCipher_.init(key); if (this.RC4_DISCARD_BYTES > 0) this.streamCipher_.discard(this.RC4_DISCARD_BYTES); this.streamCipher_.updateFromString(text); var encrypted = this.streamCipher_.finish(true /* base64 encoded */); // The base64 version we get has new lines, we want to remove those. return encrypted.replace(/\r\n/g, ""); } /** * Create an encryption key from K_C and a nonce * * @param clientKeyArray Array of bytes comprising K_C * * @param count Number that acts as a nonce for this key * * @return nsIKeyObject */ PROT_UrlCrypto.prototype.deriveEncryptionKey = function(clientKeyArray, count) { G_Assert(this, clientKeyArray instanceof Array, "Client key should be an array of bytes"); G_Assert(this, typeof count == "number", "Count should be a number"); // Don't clobber the client key by appending the nonce; use another array var paddingArray = []; paddingArray.push(count >> 24); paddingArray.push((count >> 16) & 0xFF); paddingArray.push((count >> 8) & 0xFF); paddingArray.push(count & 0xFF); this.hasher_.init(G_CryptoHasher.algorithms.MD5); this.hasher_.updateFromArray(clientKeyArray); this.hasher_.updateFromArray(paddingArray); // Create the nsIKeyObject var keyFactory = Cc["@mozilla.org/security/keyobjectfactory;1"] .getService(Ci.nsIKeyObjectFactory); var key = keyFactory.keyFromString(Ci.nsIKeyObject.RC4, this.hasher_.digestRaw()); return key; } /** * Return a new nonce for us to use. Rather than keeping a counter and * the headaches that entails, just use the low ms since the epoch. * * @returns 32-bit number that is the nonce to use for this encryption */ PROT_UrlCrypto.prototype.getCount_ = function() { return ((new Date).getTime() & 0xFFFFFFFF); } /** * Init the mac. This function is called by WireFormatReader if the update * server has sent along a mac param. The caller must not call initMac again * before calling finishMac; instead, the caller should just use another * UrlCrypto object. * * @param opt_clientKeyArray Optional clientKeyArray, for testing */ PROT_UrlCrypto.prototype.initMac = function(opt_clientKeyArray) { if (this.macInitialized_) { throw new Error("Can't interleave calls to initMac. Please use another " + "UrlCrypto object."); } this.macInitialized_ = true; var clientKeyArray = null; if (!!opt_clientKeyArray) { clientKeyArray = opt_clientKeyArray; } else { clientKeyArray = this.manager_.getClientKeyArray(); } // Don't re-use this.hasher_, in case someone calls deriveEncryptionKey // between initMac and finishMac this.macer_.init(G_CryptoHasher.algorithms.MD5); this.macer_.updateFromArray(clientKeyArray); this.macer_.updateFromArray(this.separatorArray_); } /** * Add a line to the mac. Called by WireFormatReader.processLine. Not thread * safe. * * @param s The string to add */ PROT_UrlCrypto.prototype.updateMacFromString = function(s) { if (!this.macInitialized_) { throw new Error ("Initialize mac first"); } var arr = this.base64_.arrayifyString(s); this.macer_.updateFromArray(arr); } /** * Finish up computing the mac. Not thread safe. * * @param opt_clientKeyArray Optional clientKeyArray, for testing */ PROT_UrlCrypto.prototype.finishMac = function(opt_clientKeyArray) { var clientKeyArray = null; if (!!opt_clientKeyArray) { clientKeyArray = opt_clientKeyArray; } else { clientKeyArray = this.manager_.getClientKeyArray(); } if (!this.macInitialized_) { throw new Error ("Initialize mac first"); } this.macer_.updateFromArray(this.separatorArray_); this.macer_.updateFromArray(clientKeyArray); this.macInitialized_ = false; return this.macer_.digestBase64(); } /** * Compute a mac over the whole data string, and return the base64-encoded * string * * @param data A string * @param opt_outputRaw True for raw output, false for base64 * @param opt_clientKeyArray An optional key to pass in for testing * @param opt_separatorArray An optional separator array to pass in for testing * @returns MD5(key+separator+data+separator+key) */ PROT_UrlCrypto.prototype.computeMac = function(data, opt_outputRaw, opt_clientKeyArray, opt_separatorArray) { var clientKeyArray = null; var separatorArray = null; // Get keys and such for testing if (!!opt_clientKeyArray) { clientKeyArray = opt_clientKeyArray; } else { clientKeyArray = this.manager_.getClientKeyArray(); } if (!!opt_separatorArray) { separatorArray = opt_separatorArray; } else { separatorArray = this.separatorArray_; } this.macer_.init(G_CryptoHasher.algorithms.MD5); this.macer_.updateFromArray(clientKeyArray); this.macer_.updateFromArray(separatorArray); // Note to self: calling G_CryptoHasher.updateFromString ain't the same as // arrayifying the string and then calling updateFromArray. Not sure if // that's a bug in G_CryptoHasher or not. Niels, what do you think? var arr = this.base64_.arrayifyString(data); this.macer_.updateFromArray(arr); this.macer_.updateFromArray(separatorArray); this.macer_.updateFromArray(clientKeyArray); if (!!opt_outputRaw) { return this.macer_.digestRaw(); } return this.macer_.digestBase64(); } /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Google Safe Browsing. * * The Initial Developer of the Original Code is Google Inc. * Portions created by the Initial Developer are Copyright (C) 2006 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Fritz Schneider <fritz@google.com> (original author) * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ // This file implements the tricky business of managing the keys for our // URL encryption. The protocol is: // // - Server generates secret key K_S // - Client starts up and requests a new key K_C from the server via HTTPS // - Server generates K_C and WrappedKey, which is K_C encrypted with K_S // - Server resonse with K_C and WrappedKey // - When client wants to encrypt a URL, it encrypts it with K_C and sends // the encrypted URL along with WrappedKey // - Server decrypts WrappedKey with K_S to get K_C, and the URL with K_C // // This is, however, trickier than it sounds for two reasons. First, // we want to keep the number of HTTPS requests to an aboslute minimum // (like 1 or 2 per browser session). Second, the HTTPS request at // startup might fail, for example the user might be offline or a URL // fetch might need to be issued before the HTTPS request has // completed. // // We implement the following policy: // // - Firefox will issue at most two HTTPS getkey requests per session // - Firefox will issue one HTTPS getkey request at startup if more than 24 // hours has passed since the last getkey request. // - Firefox will serialize to disk any key it gets // - Firefox will fall back on this serialized key until it has a // fresh key // - The front-end can respond with a flag in a lookup request that tells // the client to re-key. Firefox will issue a new HTTPS getkey request // at this time if it has only issued one before // We store the user key in this file. The key can be used to verify signed // server updates. const kKeyFilename = "kf.txt"; /** * A key manager for UrlCrypto. There should be exactly one of these * per appplication, and all UrlCrypto's should share it. This is * currently implemented by having the manager attach itself to the * UrlCrypto's prototype at startup. We could've opted for a global * instead, but I like this better, even though it is spooky action * at a distance. * XXX: Should be an XPCOM service * * @param opt_keyFilename String containing the name of the * file we should serialize keys to/from. Used * mostly for testing. * * @param opt_testing Boolean indicating whether we are testing. If we * are, then we skip trying to read the old key from * file and automatically trying to rekey; presumably * the tester will drive these manually. * * @constructor */ function PROT_UrlCryptoKeyManager(opt_keyFilename, opt_testing) { this.debugZone = "urlcryptokeymanager"; this.testing_ = !!opt_testing; this.base64_ = new G_Base64(); this.clientKey_ = null; // Base64-encoded, as fetched from server this.clientKeyArray_ = null; // Base64-decoded into an array of numbers this.wrappedKey_ = null; // Opaque websafe base64-encoded server key this.rekeyTries_ = 0; // Don't do anything until keyUrl_ is set. this.keyUrl_ = null; this.keyFilename_ = opt_keyFilename ? opt_keyFilename : kKeyFilename; // Convenience properties this.MAX_REKEY_TRIES = PROT_UrlCryptoKeyManager.MAX_REKEY_TRIES; this.CLIENT_KEY_NAME = PROT_UrlCryptoKeyManager.CLIENT_KEY_NAME; this.WRAPPED_KEY_NAME = PROT_UrlCryptoKeyManager.WRAPPED_KEY_NAME; if (!this.testing_) { G_Assert(this, !PROT_UrlCrypto.prototype.manager_, "Already have manager?"); PROT_UrlCrypto.prototype.manager_ = this; this.maybeLoadOldKey(); } } // Do ***** NOT ***** set this higher; HTTPS is expensive PROT_UrlCryptoKeyManager.MAX_REKEY_TRIES = 2; // Base pref for keeping track of when we updated our key. // We store the time as seconds since the epoch. PROT_UrlCryptoKeyManager.NEXT_REKEY_PREF = "urlclassifier.keyupdatetime."; // Once a day (interval in seconds) PROT_UrlCryptoKeyManager.KEY_MIN_UPDATE_TIME = 24 * 60 * 60; // These are the names the server will respond with in protocol4 format PROT_UrlCryptoKeyManager.CLIENT_KEY_NAME = "clientkey"; PROT_UrlCryptoKeyManager.WRAPPED_KEY_NAME = "wrappedkey"; /** * Called by a UrlCrypto to get the current K_C * * @returns Array of numbers making up the client key or null if we * have no key */ PROT_UrlCryptoKeyManager.prototype.getClientKeyArray = function() { return this.clientKeyArray_; } /** * Called by a UrlCrypto to get WrappedKey * * @returns Opaque base64-encoded WrappedKey or null if we haven't * gotten one */ PROT_UrlCryptoKeyManager.prototype.getWrappedKey = function() { return this.wrappedKey_; } /** * Change the key url. When we do this, we go ahead and rekey. * @param keyUrl String */ PROT_UrlCryptoKeyManager.prototype.setKeyUrl = function(keyUrl) { // If it's the same key url, do nothing. if (keyUrl == this.keyUrl_) return; this.keyUrl_ = keyUrl; this.rekeyTries_ = 0; // Check to see if we should make a new getkey request. var prefs = new G_Preferences(PROT_UrlCryptoKeyManager.NEXT_REKEY_PREF); var nextRekey = prefs.getPref(this.getPrefName_(this.keyUrl_), 0); if (nextRekey < parseInt(Date.now() / 1000, 10)) { this.reKey(); } } /** * Given a url, return the pref value to use (pref contains last update time). * We basically use the url up until query parameters. This avoids duplicate * pref entries as version number changes over time. * @param url String getkey URL */ PROT_UrlCryptoKeyManager.prototype.getPrefName_ = function(url) { var queryParam = url.indexOf("?"); if (queryParam != -1) { return url.substring(0, queryParam); } return url; } /** * Tell the manager to re-key. For safety, this method still obeys the * max-tries limit. Clients should generally use maybeReKey() if they * want to try a re-keying: it's an error to call reKey() after we've * hit max-tries, but not an error to call maybeReKey(). */ PROT_UrlCryptoKeyManager.prototype.reKey = function() { if (this.rekeyTries_ > this.MAX_REKEY_TRIES) throw new Error("Have already rekeyed " + this.rekeyTries_ + " times"); this.rekeyTries_++; G_Debug(this, "Attempting to re-key"); // If the keyUrl isn't set, we don't do anything. if (!this.testing_ && this.keyUrl_) { (new PROT_XMLFetcher()).get(this.keyUrl_, BindToObject(this.onGetKeyResponse, this)); // Calculate the next time we're allowed to re-key. var prefs = new G_Preferences(PROT_UrlCryptoKeyManager.NEXT_REKEY_PREF); var nextRekey = parseInt(Date.now() / 1000, 10) + PROT_UrlCryptoKeyManager.KEY_MIN_UPDATE_TIME; prefs.setPref(this.getPrefName_(this.keyUrl_), nextRekey); } } /** * Try to re-key if we haven't already hit our limit. It's OK to call * this method multiple times, even if we've already tried to rekey * more than the max. It will simply refuse to do so. * * @returns Boolean indicating if it actually issued a rekey request (that * is, if we haven' already hit the max) */ PROT_UrlCryptoKeyManager.prototype.maybeReKey = function() { if (this.rekeyTries_ > this.MAX_REKEY_TRIES) { G_Debug(this, "Not re-keying; already at max"); return false; } this.reKey(); return true; } /** * @returns Boolean indicating if we have a key we can use */ PROT_UrlCryptoKeyManager.prototype.hasKey_ = function() { return this.clientKey_ != null && this.wrappedKey_ != null; } /** * Set a new key and serialize it to disk. * * @param clientKey String containing the base64-encoded client key * we wish to use * * @param wrappedKey String containing the opaque base64-encoded WrappedKey * the server gave us (i.e., K_C encrypted with K_S) */ PROT_UrlCryptoKeyManager.prototype.replaceKey_ = function(clientKey, wrappedKey) { if (this.clientKey_) G_Debug(this, "Replacing " + this.clientKey_ + " with " + clientKey); this.clientKey_ = clientKey; this.clientKeyArray_ = this.base64_.decodeString(this.clientKey_); this.wrappedKey_ = wrappedKey; this.serializeKey_(this.clientKey_, this.wrappedKey_); } /** * Try to write the key to disk so we can fall back on it. Fail * silently if we cannot. The keys are serialized in protocol4 format. * * @returns Boolean indicating whether we succeeded in serializing */ PROT_UrlCryptoKeyManager.prototype.serializeKey_ = function() { var map = {}; map[this.CLIENT_KEY_NAME] = this.clientKey_; map[this.WRAPPED_KEY_NAME] = this.wrappedKey_; try { var appDir = new PROT_ApplicationDirectory(); if (!appDir.exists()) appDir.create(); var keyfile = appDir.getAppDirFileInterface(); keyfile.append(this.keyFilename_); G_FileWriter.writeAll(keyfile, (new G_Protocol4Parser).serialize(map)); return true; } catch(e) { G_Error(this, "Failed to serialize new key: " + e); return false; } } /** * Invoked when we've received a protocol4 response to our getkey * request. Try to parse it and set this key as the new one if we can. * * @param responseText String containing the protocol4 getkey response */ PROT_UrlCryptoKeyManager.prototype.onGetKeyResponse = function(responseText) { var response = (new G_Protocol4Parser).parse(responseText); var clientKey = response[this.CLIENT_KEY_NAME]; var wrappedKey = response[this.WRAPPED_KEY_NAME]; if (response && clientKey && wrappedKey) { G_Debug(this, "Got new key from: " + responseText); this.replaceKey_(clientKey, wrappedKey); } else { G_Debug(this, "Not a valid response for /getkey"); } } /** * Attempt to read a key we've previously serialized from disk, so * that we can fall back on it in case we can't get one from the * server. If we get a key, only use it if we don't already have one * (i.e., if our startup HTTPS request died or hasn't yet completed). * * This method should be invoked early, like when the user's profile * becomes available. */ PROT_UrlCryptoKeyManager.prototype.maybeLoadOldKey = function() { var oldKey = null; try { var appDir = new PROT_ApplicationDirectory(); var keyfile = appDir.getAppDirFileInterface(); keyfile.append(this.keyFilename_); if (keyfile.exists()) oldKey = G_FileReader.readAll(keyfile); } catch(e) { G_Debug(this, "Caught " + e + " trying to read keyfile"); return; } if (!oldKey) { G_Debug(this, "Couldn't find old key."); return; } oldKey = (new G_Protocol4Parser).parse(oldKey); var clientKey = oldKey[this.CLIENT_KEY_NAME]; var wrappedKey = oldKey[this.WRAPPED_KEY_NAME]; if (oldKey && clientKey && wrappedKey && !this.hasKey_()) { G_Debug(this, "Read old key from disk."); this.replaceKey_(clientKey, wrappedKey); } } /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Google Safe Browsing. * * The Initial Developer of the Original Code is Google Inc. * Portions created by the Initial Developer are Copyright (C) 2006 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Fritz Schneider <fritz@google.com> (original author) * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ // A simple class that encapsulates a request. You'll notice the // style here is different from the rest of the extension; that's // because this was re-used from really old code we had. At some // point it might be nice to replace this with something better // (e.g., something that has explicit onerror handler, ability // to set headers, and so on). // // The only interesting thing here is its ability to strip cookies // from the request. /** * Because we might be in a component, we can't just assume that * XMLHttpRequest exists. So we use this tiny factory function to wrap the * XPCOM version. * * @return XMLHttpRequest object */ function PROT_NewXMLHttpRequest() { var Cc = Components.classes; var Ci = Components.interfaces; var request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"] .createInstance(Ci.nsIXMLHttpRequest); // Need the following so we get onerror/load/progresschange request.QueryInterface(Ci.nsIJSXMLHttpRequest); return request; } /** * A helper class that does HTTP GETs and calls back a function with * the content it receives. Asynchronous, so uses a closure for the * callback. * * @param opt_stripCookies Boolean indicating whether we should strip * cookies from this request * * @constructor */ function PROT_XMLFetcher(opt_stripCookies) { this.debugZone = "xmlfetcher"; this._request = PROT_NewXMLHttpRequest(); this._stripCookies = !!opt_stripCookies; } PROT_XMLFetcher.prototype = { /** * Function that will be called back upon fetch completion. */ _callback: null, /** * Fetches some content. * * @param page URL to fetch * @param callback Function to call back when complete. */ get: function(page, callback) { this._request.abort(); // abort() is asynchronous, so this._request = PROT_NewXMLHttpRequest(); this._callback = callback; var asynchronous = true; this._request.open("GET", page, asynchronous); if (this._stripCookies) new PROT_CookieStripper(this._request.channel); // Create a closure var self = this; this._request.onreadystatechange = function() { self.readyStateChange(self); } this._request.send(null); }, /** * Called periodically by the request to indicate some state change. 4 * means content has been received. */ readyStateChange: function(fetcher) { if (fetcher._request.readyState != 4) return; // If the request fails, on trunk we get status set to // NS_ERROR_NOT_AVAILABLE. On 1.8.1 branch we get an exception // forwarded from nsIHttpChannel::GetResponseStatus. To be consistent // between branch and trunk, we send back NS_ERROR_NOT_AVAILABLE for // http failures. var responseText = null; var status = Components.results.NS_ERROR_NOT_AVAILABLE; try { G_Debug(this, "xml fetch status code: \"" + fetcher._request.status + "\""); status = fetcher._request.status; responseText = fetcher._request.responseText; } catch(e) { G_Debug(this, "Caught exception trying to read xmlhttprequest " + "status/response."); G_Debug(this, e); } if (fetcher._callback) fetcher._callback(responseText, status); } }; /** * This class knows how to strip cookies from an HTTP request. It * listens for http-on-modify-request, and modifies the request * accordingly. We can't do this using xmlhttprequest.setHeader() or * nsIChannel.setRequestHeader() before send()ing because the cookie * service is called after send(). * * @param channel nsIChannel in which the request is happening * @constructor */ function PROT_CookieStripper(channel) { this.debugZone = "cookiestripper"; this.topic_ = "http-on-modify-request"; this.channel_ = channel; var Cc = Components.classes; var Ci = Components.interfaces; this.observerService_ = Cc["@mozilla.org/observer-service;1"] .getService(Ci.nsIObserverService); this.observerService_.addObserver(this, this.topic_, false); // If the request doesn't issue, don't hang around forever var twentySeconds = 20 * 1000; this.alarm_ = new G_Alarm(BindToObject(this.stopObserving, this), twentySeconds); } /** * Invoked by the observerservice. See nsIObserve. */ PROT_CookieStripper.prototype.observe = function(subject, topic, data) { if (topic != this.topic_ || subject != this.channel_) return; G_Debug(this, "Stripping cookies for channel."); this.channel_.QueryInterface(Components.interfaces.nsIHttpChannel); this.channel_.setRequestHeader("Cookie", "", false /* replace, not add */); this.alarm_.cancel(); this.stopObserving(); } /** * Remove us from the observerservice */ PROT_CookieStripper.prototype.stopObserving = function() { G_Debug(this, "Removing observer"); this.observerService_.removeObserver(this, this.topic_); this.channel_ = this.alarm_ = this.observerService_ = null; } /** * XPCOM cruft */ PROT_CookieStripper.prototype.QueryInterface = function(iid) { var Ci = Components.interfaces; if (iid.equals(Ci.nsISupports) || iid.equals(Ci.nsIObserve)) return this; throw Components.results.NS_ERROR_NO_INTERFACE; } //@line 65 "/cygdrive/c/builds/tinderbox/Fx-Mozilla1.8-release/WINNT_5.2_Depend/mozilla/toolkit/components/url-classifier/src/nsUrlClassifierLib.js" // Expose this whole component. var lib = this; function UrlClassifierLib() { this.wrappedJSObject = lib; } // Module object function UrlClassifierLibMod() { this.firstTime = true; this.cid = Components.ID("{26a4a019-2827-4a89-a85c-5931a678823a}"); this.progid = "@mozilla.org/url-classifier/jslib;1"; } UrlClassifierLibMod.prototype.registerSelf = function(compMgr, fileSpec, loc, type) { if (this.firstTime) { this.firstTime = false; throw Components.results.NS_ERROR_FACTORY_REGISTER_AGAIN; } compMgr = compMgr.QueryInterface(Ci.nsIComponentRegistrar); compMgr.registerFactoryLocation(this.cid, "UrlClassifier JS Lib", this.progid, fileSpec, loc, type); }; UrlClassifierLibMod.prototype.getClassObject = function(compMgr, cid, iid) { if (!cid.equals(this.cid)) throw Components.results.NS_ERROR_NO_INTERFACE; if (!iid.equals(Ci.nsIFactory)) throw Components.results.NS_ERROR_NOT_IMPLEMENTED; return this.factory; } UrlClassifierLibMod.prototype.canUnload = function(compMgr) { return true; } UrlClassifierLibMod.prototype.factory = { createInstance: function(outer, iid) { if (outer != null) throw Components.results.NS_ERROR_NO_AGGREGATION; return new UrlClassifierLib(); } }; var LibModInst = new UrlClassifierLibMod(); function NSGetModule(compMgr, fileSpec) { return LibModInst; }